#include "include/lrms.h"
#include "include/protocol.h"

/* =============================================================== *
 *			Prototypes				   *
 * =============================================================== */

int create_udp_socket(int * sockfd);
int connect_to_scheduler(int sockServer, char * ip_addr, int portNum, struct sockaddr_in * addrSched);
int wait_for_job(int sockServer, struct sockaddr_in * addrSched, char * job_file_path, char * job_arguments);
int exec_job(const char * job_file_path, const char * job_arguments, char * results_file_path);
int send_results(int sockServer, struct sockaddr_in addrSched, char * results_file_path);
int exec_pg(const char * command);


/* =============================================================== *
 *			Globals Variables			   *
 * =============================================================== */

static int log_file = 0;	// log file
static char log_msg[MAXLOGMSG];


/* =============================================================== *
 *			MAIN FUNCTION				   *
 * =============================================================== */

int main(int argc, char* argv[])
{
	int sockServer;				// socket server
	int returnStatus;			// for each function call
	struct sockaddr_in addrSched;		// Scheduler address

	
	char job_file_path[MAXNAME] = "";
	char results_file_path[MAXNAME] = "";
	char job_arguments[MAXNAME] = "";
	int readCounter;
	
	
	/* ----------------------- get arguments ----------------------- */
	
	if (argc != 3)
	{
		sprintf(log_msg, "Usage: %s <Scheduler ip> <port>.\n", argv[0]);
		print_msg( log_msg, 1, 1, 0, log_file);
		exit(EXIT_FAILURE);
	}
	
	/* ------------------------ open log file ---------------------- */
	
	log_file = open( "server.log", O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR );
	
	/* --------------------- create server socket ------------------ */
	
	returnStatus = create_udp_socket( &sockServer);
	if (returnStatus != EXIT_SUCCESS )
	{
		sprintf(log_msg, "Server::Create_UDP_Socket : Error : Socket creation.\n");
		print_msg( log_msg, 0, 1, 1, log_file);
		exit(EXIT_FAILURE);
	}
	sprintf(log_msg, "Server::Create_UDP_Socket : Socket %d created successfully.\n", sockServer);
	print_msg( log_msg, 1, 0, 1, log_file);
	
	/* ------------------connect to the Scheduler -------------------*/
	
	returnStatus = connect_to_scheduler( sockServer, argv[1], atoi(argv[2]), &addrSched);
	if ( returnStatus != EXIT_SUCCESS)
	{
		sprintf(log_msg, "Server::Connect_To_Scheduler : Error : Cann't connect to the scheduler.\n");
		print_msg( log_msg, 0, 1, 1, log_file);
		exit(EXIT_FAILURE);
	}
	sprintf(log_msg, "Server::Connect_To_Scheduler : Connected to Scheduler %s@%d.\n", inet_ntoa(addrSched.sin_addr), ntohs(addrSched.sin_port));
	print_msg( log_msg, 1, 0, 1, log_file);
	
	/* --------------- receive jobs from scheduler ------------------*/
	
	while (1)
	{
		/* ----------------- wait for job demands -------------- */
		
		sprintf(log_msg,"Server::Wait for job.\n");
		print_msg( log_msg, 1, 0, 1, log_file);
		
		returnStatus =  wait_for_job(sockServer, &addrSched, job_file_path, job_arguments);
		if ( returnStatus != EXIT_SUCCESS)
		{
			sprintf(log_msg, "Server::Wait_For_Job : Error : Cann't receive job.\n");
			print_msg( log_msg, 0, 1, 1, log_file);
			continue;
		}
		sprintf(log_msg, "Server::Wait_For_Job : Job received .\n");
		print_msg( log_msg, 1, 0, 1, log_file);
		
		/* -------------------- job execution ------------------ */
		
		returnStatus = exec_job( job_file_path, job_arguments, results_file_path);
		if ( returnStatus != EXIT_SUCCESS)
		{
			sprintf(log_msg, "Server::Exec_Job : Error : Cann't execute job.\n");
			print_msg( log_msg, 0, 1, 1, log_file);
			exit(EXIT_FAILURE);
		}
		
		sprintf(log_msg, "Server::Exec_Job : Execution Done .\n");
		print_msg( log_msg, 1, 0, 1, log_file);
		
		/* ------------- send results to the shecduler --------- */
		
		returnStatus = send_results(sockServer, addrSched, results_file_path);
		if ( returnStatus != EXIT_SUCCESS)
		{
			sprintf(log_msg, "Server::Send_Results : Error : Cann't send results to scheduler.\n");
			print_msg( log_msg, 0, 1, 1, log_file);
			exit(EXIT_FAILURE);
		}
		
		sprintf(log_msg, "Server::Send_Results : Results sended successfully .\n");
		print_msg( log_msg, 1, 0, 1, log_file);
	}
	
	sprintf(log_msg, "Server::Exited by Scheduler (%s).\n", term);
	print_msg( log_msg, 1, 0, 1, log_file);
	
	/* cleanup */
	close(log_file);
	close(sockServer);
	return EXIT_SUCCESS;
}

/* =============================================================== *
 *		       CREATE UDP SOCKET			   *
 * =============================================================== */

int create_udp_socket(int * sockfd)
{
	*sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (*sockfd < 0)
	{
		perror("socket");
		return EXIT_FAILURE;
	}
	
	return EXIT_SUCCESS;
}

/* =============================================================== *
 *		       CONNECT TO SCHEDULER			   *
 * =============================================================== */

int connect_to_scheduler(int sockServer, char * addr, int portNum, struct sockaddr_in * addrSched)
{
	char bufctrl[MAXBUFCTRL];
	int returnStatus = 0;
	void * pReturnStatus = NULL;
	socklen_t addrlen;			// size of sockaddr_in
	
	/* init the addrSched */
	pReturnStatus = memset(addrSched, 0, sizeof(struct sockaddr_in));
	if( pReturnStatus == NULL) {
		perror("memset");
		return EXIT_FAILURE;
	}
	
	/* construct the addrSched */
	addrSched->sin_family = AF_INET;
	(addrSched->sin_addr).s_addr = inet_addr(addr);
	addrSched->sin_port = htons(portNum);
	
	/* send register to the scheduler */
	strcpy(bufctrl, reg);	
	returnStatus = sendto(sockServer, bufctrl, strlen(reg), 0,(SA*) addrSched, sizeof(*addrSched));
	if (returnStatus < 0) 
	{
		perror("sendto");
		return EXIT_FAILURE;
	}
	
	//sprintf(log_msg, "Server::Connect_To_Scheduler : Wait for response from %s@%d.\n", inet_ntoa(addrSched->sin_addr), ntohs(addrSched->sin_port));
	//print_msg( log_msg, 1, 0, 1, log_file);
	
	/* receive acknowledge from scheduler */
	do {
		addrlen = sizeof(*addrSched);
		returnStatus = recvfrom(sockServer, bufctrl, returnStatus, 0,(SA*)addrSched, &addrlen);
		if (returnStatus < 0) 
		{
			perror("recvfrom");
			sprintf(log_msg, "Could not receive acknowledge (%s).\n",reg);
			print_msg( log_msg, 0, 1, 1, log_file);
			return EXIT_FAILURE; 
		}
		
		bufctrl[returnStatus] = '\0';
		
	} while( strcmp(bufctrl,reg) != 0 );
	

	return EXIT_SUCCESS;
	
}

/* =============================================================== *
 *		            WAIT FOR JOB			   *
 * =============================================================== */

int wait_for_job(int sockServer, struct sockaddr_in * addrSched, char * job_file_path, char * job_arguments)
{

	int returnStatus = 0;
	socklen_t addrlen;			// size of sockaddr_in
	char buffile[MAXBUFFILE];		// File Buffer
	char bufctrl[MAXBUFCTRL];		// Controll buffer
	char progname[MAXNAME] = "";
	static int prognamec = 0;
	char argts[MAXNAME] =" " ; 
	int progfd = 0;
	char temp[40];

	/* wait for job demands */
	do {
		addrlen = sizeof(*addrSched);
		returnStatus = recvfrom(sockServer, bufctrl, strlen(job), 0,(SA*)addrSched, &addrlen);
		if (returnStatus < 0) 
		{
			perror("recvfrom");
			return EXIT_FAILURE;
		}
			
		// plus 
		bufctrl[returnStatus] = '\0';
		
		/* verifing if it is a term message 
		if ( strcmp(bufctrl, term) == 0 ) break;*/
			
	} while( strcmp(bufctrl,job) != 0 );
		
	/* verifingg if it is a term message 
	if ( strcmp(bufctrl, term) == 0 ) //break;*/
	
	// debug 
	sprintf(log_msg,"Server::Wait_For_Job : Recv %s from %s@%d \n", bufctrl, inet_ntoa(addrSched->sin_addr), ntohs(addrSched->sin_port));
	print_msg( log_msg, 1, 0, 1, log_file);
		
	/* connect the server socket to the new scheduler socket */	
	//connect(sockServer, (struct sockaddr*)addrSched, sizeof(*addrSched));	
		
	/* create file to be read from socket */
	/* open file  for writing */ 
	sprintf(progname, "%s%d", prgnm, prognamec);
	prognamec += 1;
	progfd = open(progname, O_WRONLY | O_CREAT , S_IXUSR | S_IRUSR | S_IWUSR);
	if(progfd < 0) 	
	{
	 	perror("open");
	 	return EXIT_FAILURE;
	}
		
	/* receive pg */
	while (1) {
		
		returnStatus = recvfrom(sockServer, buffile, MAXBUFFILE, 0,(SA*)addrSched, &addrlen);
		if (returnStatus < 0) 
		{
			perror("recvfrom");
			return EXIT_FAILURE;
		}
			
		// plus
		buffile[returnStatus] = '\0';
		
		/* exit job 
		if( strcmp(buffile, jobterm) == 0 ) break;*/
			
		/* end of file pg */
		if( strcmp(buffile, progterm) == 0 ) break;
			
		/* write to file */
		write(progfd, buffile, returnStatus);
			
	}
		
	/* close file for writing */
	close(progfd);
		
	/* exit job 
	if( strcmp(buffile, jobterm) == 0 ) //continue;*/
		
	/* Reciving arguments */
	while(1){
		
		returnStatus = recvfrom(sockServer, buffile, MAXBUFFILE, 0,(SA*)addrSched, &addrlen);
			
		if (returnStatus < 0) 
		{
			perror("recvfrom");
			return EXIT_FAILURE;
		}
					
		/* Check if it is a argterm message */
		buffile[returnStatus] = '\0';
		if(strcmp(buffile,argterm)==0) break;	
			
		/* Copy argument */
		sprintf(temp, " %s", buffile);
		strcat(argts,temp);
				
	}
	
	/* copy the job path/args  */
	strcpy(job_file_path, progname);
	strcpy(job_arguments, argts);
	return EXIT_SUCCESS;
		
}

/* =============================================================== *
 *		              EXEC JOB				   *
 * =============================================================== */
	
int exec_job(const char * job_file_path, const char * job_arguments, char * results_file_path)
{
	int returnStatus = 0;
	char outputname[MAXNAME];
	char command[100];
	
	/* copy the output file name */
	returnStatus = sprintf(outputname, "%s.output", job_file_path);
	if (returnStatus < 0) 
	{
		perror("sprintf");
		return EXIT_FAILURE;
	}
	
	/* copy the command name */
	returnStatus = sprintf(command, "./%s %s > %s 2>&1", job_file_path, job_arguments, outputname);
	if (returnStatus < 0) 
	{
		perror("sprintf");
		return EXIT_FAILURE;
	}
	
	/* execute the job */
	sprintf(log_msg, "Server::Exec_Job : Executing %s \n", command);
	//print_msg( log_msg, 1, 0, 1, log_file);
	
	returnStatus = exec_pg(command);
	if (returnStatus != EXIT_SUCCESS) 
	{
		sprintf(log_msg, "Server::Exec_Job : Execution Error .\n");
		print_msg( log_msg, 0, 1, 1, log_file);
		return EXIT_FAILURE;
	}
	
	/* copy the result file path */
	strcpy( results_file_path, outputname);
	
	return EXIT_SUCCESS;
}


/* =============================================================== *
 *		          SEND RESULTS				   *
 * =============================================================== */
	
int send_results(int sockServer, struct sockaddr_in addrSched, char * results_file_path)
{
	char buffile[MAXBUFFILE];		// File Buffer
	char bufctrl[MAXBUFCTRL];		// Controll buffer
	int readCounter, returnStatus;
	int results_fd ;
	
	results_fd = open( results_file_path, O_RDONLY );
	if(results_fd < 0) 	
	{
	 	perror("open");
	 	return EXIT_FAILURE;
	}
	
	sprintf(log_msg, "Server::Send_Results : Sending Results to %s@%d .\n", inet_ntoa(addrSched.sin_addr), ntohs(addrSched.sin_port));
	print_msg( log_msg, 1, 0, 1, log_file);
	
	while((readCounter = read(results_fd, buffile, MAXBUFFILE)) > 0 )
	{
		returnStatus = sendto(sockServer, buffile, readCounter, 0, (SA*)&addrSched, sizeof(addrSched));
		if (returnStatus < 0) 
		{
			perror("sendto");
			return EXIT_FAILURE;
		}
		//sprintf(log_msg, "Server::Send %s to %s@%d \n", buffile, inet_ntoa(addrSched.sin_addr), ntohs(addrSched.sin_port));
		//print_msg( log_msg, 1, 0, 1, log_file);
			
	}
		
	strcpy(buffile, resultsterm);
	returnStatus = sendto(sockServer, buffile, strlen(resultsterm), 0,(SA*)&addrSched, sizeof(addrSched));
	if (returnStatus < 0) 
	{
		perror("sendto");
		return EXIT_FAILURE;
	}
		
	//sprintf(log_msg, "Server::Send %s to %s@%d \n", buffile, inet_ntoa(addrSched.sin_addr), ntohs(addrSched.sin_port));
	//print_msg( log_msg, 1, 0, 1, log_file);
		
	close(results_fd);	
	
}

/* =============================================================== *
 *		          EXEC PG				   *
 * =============================================================== */

int exec_pg(const char * exec_command)
{
	char *argv[4];
	int retour;
	pid_t pid;
	
	if ((pid = fork()) < 0)
	/* erreur dans fork */
		return EXIT_FAILURE;
	
	if (pid == 0) {
		/* processus fils */
		argv[0] = "sh";
		argv[1] = "-c";
		strcpy(argv[2], exec_command);
		argv[3] = (char *) NULL;
		execv("/bin/sh", argv);
		
		/* execv a échoué */
		exit(127);
	}
	
	/* processus père */
	/* attente de la fin du processus fils */
	while (waitpid(pid, & retour, 0) < 0)
		if (errno != EINTR)
			return EXIT_FAILURE;
	
	return EXIT_SUCCESS;

}



